package com.lds.sign_sdk.interceptor;


import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.time.chrono.ChronoLocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import com.lds.sign_sdk.annotation.FieldEncrypt;
import com.lds.sign_sdk.crypto.CryptoProperties;
import com.lds.sign_sdk.crypto.ICrypto;
import com.lds.sign_sdk.emnu.Algorithm;
import com.lds.sign_sdk.emnu.CryptoType;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @Date 2022/7/29
 * @Time 16:31
 * @Author liudongs
 * @E-mail liudongs@aliyun.com
 * @Version 1.0.0
 * @Description
 **/
@Intercepts({@Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
), @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
), @Signature(
        type = Executor.class,
        method = "update",
        args = {MappedStatement.class, Object.class}
)})
@Component
public class CryptoInterceptor implements Interceptor {
    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    @Autowired
    private CryptoProperties cryptoProperties;

    public CryptoInterceptor() {
    }

    public Object intercept(Invocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        String var3 = method.getName();
        byte var4 = -1;
        switch(var3.hashCode()) {
            case -838846263:
                if (var3.equals("update")) {
                    var4 = 0;
                }
                break;
            case 107944136:
                if (var3.equals("query")) {
                    var4 = 1;
                }
        }

        switch(var4) {
            case 0:
                return this.updateHandle(invocation);
            case 1:
                return this.selectHandle(invocation);
            default:
                return invocation.proceed();
        }
    }

    private Object selectHandle(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement)args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds)args[2];
        org.apache.ibatis.session.ResultHandler<Object> resultHandler = (org.apache.ibatis.session.ResultHandler)args[3];
        org.apache.ibatis.executor.Executor executor = (org.apache.ibatis.executor.Executor)invocation.getTarget();
        this.handleParameterOrResult(parameter, CryptoType.ENCRYPT);
        CacheKey cacheKey;
        BoundSql boundSql;
        if (args.length == 4) {
            boundSql = ms.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            cacheKey = (CacheKey)args[4];
            boundSql = (BoundSql)args[5];
        }

        List<Object> resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
        Iterator var11 = resultList.iterator();

        while(var11.hasNext()) {
            Object o = var11.next();
            this.handleParameterOrResult(o, CryptoType.DECRYPT);
        }

        return resultList;
    }

    private Object updateHandle(Invocation invocation) throws Throwable {
        this.handleParameterOrResult(invocation.getArgs()[1], CryptoType.ENCRYPT);
        return invocation.proceed();
    }

    private void handleParameterOrResult(Object object, CryptoType cryptoType) throws IllegalAccessException {
        HashMap<Field, Object> fieldObjectHashMap = new HashMap();
        if (object instanceof Map) {
            Map paramMap = (Map)object;
            Set keySet = paramMap.keySet();
            Iterator var6 = keySet.iterator();

            while(var6.hasNext()) {
                Object key = var6.next();
                Object o = paramMap.get(key);
                if (o != null) {
                    this.handleObject(o, o.getClass(), fieldObjectHashMap);
                }
            }
        } else if (object != null) {
            this.handleObject(object, object.getClass(), fieldObjectHashMap);
        }

        fieldObjectHashMap.keySet().forEach((keyx) -> {
            try {
                this.handleString(keyx, fieldObjectHashMap.get(keyx), cryptoType);
            } catch (Exception var5) {
                var5.printStackTrace();
            }

        });
    }

    private boolean isBase(Type type) {
        return Boolean.TYPE.equals(type) || Character.TYPE.equals(type) || Long.TYPE.equals(type) || Integer.TYPE.equals(type) || Byte.TYPE.equals(type) || Short.TYPE.equals(type) || Double.TYPE.equals(type) || Float.TYPE.equals(type);
    }

    private boolean isFilter(Object object) {
        return object == null || object instanceof CharSequence || object instanceof Number || object instanceof Collection || object instanceof Date || object instanceof ChronoLocalDate;
    }

    private List<Field> mergeField(Class<?> oClass, List<Field> fields) {
        if (fields == null) {
            fields = new ArrayList();
        }

        Class<?> superclass = oClass.getSuperclass();
        if (superclass != null && !superclass.equals(Object.class) && superclass.getDeclaredFields().length > 0) {
            this.mergeField(superclass, (List)fields);
        }

        Field[] var4 = oClass.getDeclaredFields();
        int var5 = var4.length;

        for(int var6 = 0; var6 < var5; ++var6) {
            Field declaredField = var4[var6];
            int modifiers = declaredField.getModifiers();
            if (!Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers) && !Modifier.isVolatile(modifiers) && !Modifier.isSynchronized(modifiers)) {
                ((List)fields).add(declaredField);
            }
        }

        return (List)fields;
    }

    private void handleObject(Object obj, Class<?> oClass, HashMap<Field, Object> fieldObjectHashMap) throws IllegalAccessException {
        if (!this.isFilter(obj)) {
            List<Field> fields = this.mergeField(oClass, (List)null);
            Iterator var5 = fields.iterator();

            while(true) {
                while(true) {
                    Field declaredField;
                    Object value;
                    do {
                        do {
                            do {
                                if (!var5.hasNext()) {
                                    return;
                                }

                                declaredField = (Field)var5.next();
                            } while(Modifier.isStatic(declaredField.getModifiers()));

                            boolean accessible = declaredField.isAccessible();
                            declaredField.setAccessible(true);
                            value = declaredField.get(obj);
                            declaredField.setAccessible(accessible);
                        } while(value == null);
                    } while(value instanceof Number);

                    if (value instanceof String) {
                        FieldEncrypt annotation = (FieldEncrypt)declaredField.getAnnotation(FieldEncrypt.class);
                        if (annotation != null) {
                            fieldObjectHashMap.put(declaredField, obj);
                        }
                    } else if (value instanceof Collection) {
                        Collection coll = (Collection)value;
                        Iterator var10 = coll.iterator();

                        while(var10.hasNext()) {
                            Object o = var10.next();
                            if (this.isFilter(o)) {
                                break;
                            }

                            this.handleObject(o, o.getClass(), fieldObjectHashMap);
                        }
                    } else {
                        this.handleObject(value, value.getClass(), fieldObjectHashMap);
                    }
                }
            }
        }
    }

    private void handleString(Field field, Object object, CryptoType cryptoType) throws Exception {
        boolean accessible = field.isAccessible();
        field.setAccessible(true);
        Object value = field.get(object);
        FieldEncrypt annotation = (FieldEncrypt)field.getAnnotation(FieldEncrypt.class);
        if (annotation != null) {
            String propertiesKey = this.cryptoProperties.getKey();
            logger.debug("全局key是：" + propertiesKey);
            String annotationKey = annotation.key();
            logger.debug("注解key是：" + annotationKey);
            String key;
            if (!"".equals(annotationKey)) {
                key = annotationKey;
            } else {
                key = propertiesKey;
            }

            Algorithm algorithm = annotation.algorithm();
            Class<? extends ICrypto> iCryptoImpl = annotation.iCrypto();
            ICrypto iCrypto = (ICrypto)iCryptoImpl.newInstance();
            String valueResult;
            if (cryptoType.equals(CryptoType.DECRYPT)) {
                valueResult = iCrypto.decrypt(algorithm, String.valueOf(value), key);
            } else {
                valueResult = iCrypto.encrypt(algorithm, String.valueOf(value), key);
            }

            logger.debug("原值：" + value);
            logger.debug("现在：" + valueResult);
            field.set(object, String.valueOf(valueResult));
            field.setAccessible(accessible);
        }

    }


    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }


    public void setProperties(Properties properties) {
    }
}
